データベースマイグレーション
==================

> Note|注意: データベースマイグレーション機能はバージョン 1.1.6 以降で利用可能です。

データベース駆動型のアプリケーションを開発し保守していると、ソースコードと同じように、データベースの構造も徐々に進化していきます。
例えば、開発中に、新しいテーブルを追加したくなることがあります。
またアプリケーションが運用段階に入った後で、あるカラムにインデックスを追加する必要があることに気付くかも知れません。
このような構造的なデータベース変更 (**マイグレーション**と呼びます) の経過を記録することは、ソースコードの変更を記録するのと同じように、非常に重要なことです。
ソースコードとデータベースの同期が崩れた場合には、全体のシステムが壊れる可能性が非常に大きいのです。
こういう理由から、Yii はデータベースマイグレーションツールを提供して、データベースマイグレーションの履歴を管理し、新しいマイグレーションを適用したり、既存のマイグレーションを破棄したりすることを可能にしています。

下記の例は、開発中にどのようにデータベースマイグレーションを使うことが出来るかを示します。

1. Tim が新しいマイグレーション (例えば、新しいテーブルの作成) を作る
2. Tim が新しいマイグレーションをソースコントロールシステム (例えば SVN や GIT) にコミットする
3. Doug がソースコントロールシステムから更新して新しいマイグレーションを受け取る
4. Doug がマイグレーションをローカルの開発用データベースに適用する

Yii はデータベースマイグレーションを `yiic migrate` コマンドラインツールによってサポートします。
このツールは、新しいマイグレーションの作成、マイグレーションの適用・破棄・再適用、および、マイグレーションの履歴と新規マイグレーションの閲覧をサポートしています。

以下、このツールの使い方を説明します。

> Note|注意: `migrate` コマンドを使うときには、`framework` ディレクトリで作業するのでなく、
> アプリケーション固有のディレクトリに入って (例えば `cd path/to/protected`) yiic を使う方が良いでしょう。
> `protected\migrations` ディレクトリが存在しており、書き込み可能であることを確かめてください。
> また、`protected/config/console.php` においてデータベース接続を構成したかどうかもチェックしてください。


マイグレーションを作成する
-------------------

新しいマイグレーション(例えば、ニュースのテーブルの作成)を作成するためには、下記のコマンドを実行します。

~~~
yiic migrate create <name>
~~~

要求される `name` パラメータには、マイグレーションの非常に短い説明 (例えば `create_news_table`) を指定します。
後述するように、この `name` パラメータは PHP のクラス名の一部として使用されます。
したがって、アルファベット、数字、および/または、アンダースコアだけで構成しなければなりません。

~~~
yiic migrate create create_news_table
~~~

上記のコマンドは、`protected/migrations` ディレクトリに、`m101129_185401_create_news_table.php` という名前の新しいファイルを作成します。
このファイルは初期状態では以下の内容を含んでいます。

~~~
[php]
class m101129_185401_create_news_table extends CDbMigration
{
	public function up()
	{
	}

	public function down()
	{
		echo "m101129_185401_create_news_table does not support migration down.\n";
		return false;
	}

	/*
	// implement safeUp/safeDown instead if transaction is needed
	public function safeUp()
	{
	}

	public function safeDown()
	{
	}
	*/
}
~~~

クラス名はファイル名と同じで、`m<timestamp>_<name>` というパターンです。
`<timestamp>` はマイグレーションが作成された UTC タイムスタンプ(`yymmdd_hhmmss` の形式)を示し、`<name>` はコマンドの `name` パラメータから取られます。

`up()` メソッドが、実際のデータベースマイグレーションを実装するコードを含むべきメソッドです。そして `down()` メソッドは、`up()` で実行されたことを破棄するコードを含むことが出来ます。

場合によっては、`down()` を実装することが不可能なことがあります。
例えば、`up()` の中でテーブルの行を削除した場合、`down()` の中で行を復元することは出来ません。
このような場合を、データベースの状態を以前の状態にロールバックすることが出来ないという意味で、不可逆なマイグレーションと呼びます。
上記の生成されたコードでは、`down()` メソッドは `false` を返して、このマイグレーションが破棄できないことを示しています。

> Info|情報: バージョン 1.1.7 からは、`up()` メソッドまたは `down()` メソッドが `false` を返すと、後続するマイグレーションの実行がすべてキャンセルされるようになりました。
> 前のバージョン 1.1.6 においては、後続するマイグレーションをキャンセルするためには、例外を投げる必要がありました。

例として、ニュースのテーブルを作成するためのマイグレーションを示しましょう。

~~~
[php]
class m101129_185401_create_news_table extends CDbMigration
{
	public function up()
	{
		$this->createTable('tbl_news', array(
			'id' => 'pk',
			'title' => 'string NOT NULL',
			'content' => 'text',
		));
	}

	public function down()
	{
		$this->dropTable('tbl_news');
	}
}
~~~

基本クラスである [CDbMigration] が、データベースのデータとスキーマを操作するために、一連のメソッドを提供しています。
例えば、[CDbMigration::createTable] はデータベーステーブルを作成しますし、[CDbMigration::insert] はデータを一行挿入します。
これらのメソッドは、すべて、[CDbMigration::getDbConnection()] が返すデータベース接続(デフォルトでは、`Yii::app()->db`)を使用します。

> Info|情報: お気付きのように、[CDbMigration] によって提供されるメソッドは [CDbCommand] のものと非常によく似ています。
実際、[CDbMigration] のメソッドが実行にかかった時間を計測することと、メソッドのパラメータに関していくつかメッセージを表示することを除けば、両者はほとんど同じです。


トランザクションを使うマイグレーション
------------------------

> Info|情報: トランザクションを使うマイグレーションはバージョン 1.1.7 以降でサポートされています。

複雑な DB マイグレーションを実行するとき、通常は、データベースが一貫性と整合性を維持できるように、個別のマイグレーションが全体として成功または失敗するように配慮する必要があります。
この目的を達成するために、DB のトランザクション機能を利用することが出来ます。

これまででも、下記のように、明示的に DB トランザクションを開始して、DB 関連のコードの残りをトランザクションで囲むことが出来ました。

~~~
[php]
class m101129_185401_create_news_table extends CDbMigration
{
	public function up()
	{
		$transaction=$this->getDbConnection()->beginTransaction();
		try
		{
			$this->createTable('tbl_news', array(
				'id' => 'pk',
				'title' => 'string NOT NULL',
				'content' => 'text',
			));
			$transaction->commit();
		}
		catch(Exception $e)
		{
			echo "Exception: ".$e->getMessage()."\n";
			$transaction->rollback();
			return false;
		}
	}

	// ... down() でも同様のコード
}
~~~

しかし、もっと簡単にトランザクションのサポートを追加する方法があります。
それは、`up()` メソッドの代りに `safeUp()` メソッドを実装し、`down()` メソッドの代りに `safeDown()` メソッドを実装するという方法です。
例えば

~~~
[php]
class m101129_185401_create_news_table extends CDbMigration
{
	public function safeUp()
	{
		$this->createTable('tbl_news', array(
			'id' => 'pk',
			'title' => 'string NOT NULL',
			'content' => 'text',
		));
	}

	public function safeDown()
	{
		$this->dropTable('tbl_news');
	}
}
~~~

Yii はマイグレーションを実行する時に、DB トランザクションを開始して、それから `safeUp()` あるいは `safeDown()` を呼びます。
`safeUp()` または `safeDown()` の中で何らかの DB エラーが発生した場合は、トランザクションがロールバックされます。
こうすることで、データベースが整合性の取れた状態に保たれることを保証します。

> Note|注意: すべての DBMS がトランザクションをサポートしている訳ではありません。
> また、DB クエリの中には、トランザクションの中に入れることが出来ないものもあります。
> その場合には、代りに `up()` または `down()` を実装しなければなりません。
> また、MySQL と MariaDB の場合、[暗黙のコミット] を引き起こす SQL 文があります。
> (詳細は、[MySQL](http://dev.mysql.com/doc/refman/5.1/en/implicit-commit.html) および
> [MariaDB](https://mariadb.com/kb/en/sql-statements-that-cause-an-implicit-commit/) の
> ドキュメントを参照して下さい。)


マイグレーションを適用する
-------------------

利用できるすべての新しいマイグレーションを適用する (すなわち、ローカルのデータベースを up-to-date に更新する) ためには、以下のコマンドを実行します。

~~~
yiic migrate
~~~

コマンドを実行すると、すべての新しいマイグレーションが一覧表示されます。
マイグレーションの適用を確認すると、クラス名のタイムスタンプの順に、一つずつ、すべての新しいマイグレーションクラスの `up()` メソッドが実行されます。

マイグレーションツールは、各マイグレーションの適用後に `tbl_migration` という名前のデータベーステーブルに記録を残します。
これによって、ツールは、どのマイグレーションが適用済みで、どのマイグレーションが適用されていないかを特定することが出来ます。
`tbl_migration` テーブルが存在しない場合は、ツールは `db` アプリケーションコンポーネントによって示されるデータベースの中にテーブルを自動的に作成します。

時として、新しいマイグレーションのうちの一つないし数個だけを適用したい場合があります。その場合は、次のコマンドを使うことが出来ます。

~~~
yiic migrate up 3
~~~

このコマンドは、新しいマイグレーションを 3 個適用します。3 という値を変更して、適用されるマイグレーションの数を変更することが出来ます。

また、下記のコマンドを使って、特定のバージョンまでデータベースをマイグレートすることも可能です。

~~~
yiic migrate to 101129_185401
~~~

すなわち、マイグレーション名のタイムスタンプ部分を使って、データベースをマイグレートして到達したいバージョンを指定する訳です。
最後に適用されたマイグレーションと指定したマイグレーションの間に複数のマイグレーションがある場合は、それらのマイグレーションがすべて適用されます。
指定したマイグレーションが適用済みである場合は、その後に適用されたすべてのマイグレーションが破棄されます(次の章で説明します)。

マイグレーションを破棄する
--------------------

適用された最後のマイグレーション (一個または複数個) を破棄したい場合は、下記のコマンドを使うことが出来ます。

~~~
yiic migrate down [step]
~~~

オプションの `step` パラメータは何個のマイグレーションを破棄するかを指定するものです。デフォルト値は 1 で、適用された最後のマイグレーションだけを破棄することを意味します。

前に説明したように、すべてのマイグレーションを破棄できない場合も有り得ます。
不可逆のマイグレーションを破棄しようとすると例外が投げられて、破棄プロセス全体が終了させられます。


マイグレーションを再適用する
------------------

マイグレーションの再適用とは、指定されたマイグレーションを最初に破棄して、それからもう一度適用することを意味します。
これは次のコマンドで実行することが出来ます。

~~~
yiic migrate redo [step]
~~~

オプションの `step` パラメータは何個のマイグレーションを再適用するかを指定するものです。デフォルト値は 1 で、最後のマイグレーションだけを再適用することを意味します。

マイグレーション情報を表示する
-----------------------------

マイグレーションを適用したり破棄したりする他に、マイグレーションツールはマイグレーションの履歴、および、まだ適用されていない新しいマイグレーションを表示することも出来ます。

~~~
yiic migrate history [limit]
yiic migrate new [limit]
~~~

オプションの `limit` パラメータは何個のマイグレーションを表示するかを指定するものです。`limit` が指定されない場合は、当てはまるすべてのマイグレーションが表示されます。

最初のコマンドは適用済みのマイグレーションを表示し、第二のコマンドは適用が済んでいないマイグレーションを表示します。


マイグレーション履歴を修正する
---------------------------

時として、実際には関係のあるマイグレーションを適用または破棄することなく、マイグレーション履歴を特定のマイグレーションバージョンに修正したい場合があります。
このことは新しいマイグレーションを開発するときにしばしば起ります。
次のコマンドを使ってこの目的を達することが出来ます。

~~~
yiic migrate mark 101129_185401
~~~

このコマンドは `yiic migrate to` コマンドと全く同じですが、マイグレーションを適用または破棄せずに、マイグレーション履歴テーブルを指定されたバージョンに修正することだけを行うという点で違っています。


マイグレーションコマンドをカスタマイズする
-----------------------------

マイグレーションコマンドをカスタマイズする方法がいくつか有ります。

### コマンドラインオプションを使う

マイグレーションコマンドには、コマンドラインで指定できる五つのオプションが有ります。

* `interactive`: 真偽値。マイグレーションを対話モードで実行するかどうかを指定します。
デフォルト値は true で、指定されたマイグレーションを実行するときに、ユーザは何らかの入力を促されます。
マイグレーションをバックグラウンドプロセスで実行する必要がある場合はこれを false にセットして下さい。

* `migrationPath`: 文字列。全てのマイグレーションクラスのファイルを保存しているディレクトリを指定します。
このパスは、パスエイリアスの形式で指定されなければならず、また、対応するディレクトリが実在しているものである必要があります。
このオプションが指定されない場合は、アプリケーションのベースパスの下の `migrations` サブディレクトリが使われます。

* `migrationTable`: 文字列。マイグレーション履歴の情報を保存するためのデータベーステーブル名を指定します。
デフォルト値は `tbl_migration` です。このテーブルは、`version varchar(255) primary key, apply_time integer` という構造を持ちます。

* `connectionID`: 文字列。database アプリケーションコンポーネントの ID を指定します。
デフォルト値は 'db' です。

* `templateFile`: 文字列。マイグレーションクラスを生成するためのコードテンプレートとして使われるファイルのパスを指定します。
このパスは、パスエイリアスの形式で指定しなければなりません(例えば、`application.migrations.template`)。
指定されない場合は、内部テンプレートが使用されます。
テンプレートの中の `{ClassName}` というトークンが実際のマイグレーションクラス名によって置き換えられます。

これらのオプションを指定するためには、下記の書式を使って migrate コマンドを実行します。

~~~
yiic migrate up --option1=value1 --option2=value2 ...
~~~

例えば、`forum` モジュールのためのマイグレーションを実施する場合で、マイグレーションファイルがモジュールの `migrations` ディレクトリに置かれているなら、下記のようにコマンドを実行します。

~~~
yiic migrate up --migrationPath=ext.forum.migrations
~~~

コマンドラインを使って `interactive` のような真偽値のオプションを設定する場合には、
以下のように `1` か `0` を渡す必要があることに注意して下さい。

~~~
yiic migrate --interactive=0
~~~

### コマンドをグローバルに構成する

コマンドラインオプションを使ってマイグレーションコマンドをその場その場で構成することも出来ますが、場合によっては、コマンドの構成を一度にまとめてしておきたいこともあります。
例えば、マイグレーションの履歴を保存するのに別のテーブルを使用したいとか、カスタマイズしたマイグレーションテンプレートを使用したいとかです。
このことは、コンソールアプリケーションの初期構成ファイルを以下のように修正することによって可能になります。

~~~
[php]
return array(
	......
	'commandMap'=>array(
		'migrate'=>array(
			'class'=>'system.cli.commands.MigrateCommand',
			'migrationPath'=>'application.migrations',
			'migrationTable'=>'tbl_migration',
			'connectionID'=>'db',
			'templateFile'=>'application.migrations.template',
		),
		......
	),
	......
);
~~~

これで、毎回コマンドラインオプションを入力しなくても、`migrate` コマンドを実行すれば、上記の設定が有効になります。
